/**
 * combobox - jQuery xui
 *
 * Licensed under the Apache v2
 *
 * Copyright 2015 xjb [ beymy.en@gmail.com ]
 *
 */
(function($) {
    
    function initDom(target) {
		var state = $.data(target, 'combobox');
        var opts = state.options;
        var t = $(target).addClass('combobox-text');
        
        var combo = t.wrap('<span class="combobox"></span>').parent();
        state.combo = combo;
        
        $('<input type="hidden" class="combobox-value">').attr('name', t.attr('name')).appendTo(combo);
        
        if(opts.hasArrow) {
        	$('<span class="combobox-addon"><i class="combobox-icon combobox-arrow"></i></span>').appendTo(combo);
        }
        
        t.removeAttr('name');
	}
	
    function init(target) {
    	var state = $.data(target, 'combobox');
    	var opts = state.options;
    	var combo = state.combo;
    	
    	$(target).addClass('combobox-f');
    	
    	if(!state.panel) {
        	var panel = $('<ul class="combobox-panel"></ul>').appendTo('body');
        	state.panel = panel;
    	}
		
		$('.combobox-text', combo).validatebox($.extend({
			required: opts.required,
			validType: opts.validType,
			novalidate: opts.novalidate
		},  opts.validateOptions));
    }

    function setSize(target) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = state.combo;
        var panel = state.panel;
        
        var addonWidth = 20; //t.children('.combobox-addon').width();
        combo.children('.combobox-text')._outerWidth(combo.width()-addonWidth);
    }

    function bindEvents(target) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = state.combo;
        var panel = state.panel;
        
        var input = combo.children('.combobox-text');
        var arrow = combo.children('.combobox-addon');
        var items = panel.children('li.combobox-item');

        //全局事件
        $(document).unbind('.combobox').bind('mousedown.combobox', function(e) {
            //隐藏没有击中的combobox的下拉面板
            var p = $(e.target).closest('ul.combobox-panel');
            var panels = $('body>ul.combobox-panel:visible');
            if (p.length) {
                panels = panels.not(p[0]);
            }
            p = $(e.target).closest('.combobox-f');
            if (p.length) {
                p = p.combobox('panel');
                panels = panels.not(p);
            }
            panels.css('display', 'none');
        }).bind('mousewheel.combobox', function(e) {
            //滚动页面时隐藏下拉面板
            var p = $(e.target).closest('ul.combobox-panel');
            var panels = $('body>ul.combobox-panel:visible');
            if (p.length) {
                panels = panels.not(p[0]);
            }
            panels.css('display', 'none');
        });

        //先取消绑定事件
        input.unbind('.combobox');
        arrow.unbind('.combobox');
        items.unbind('.combobox');

        if (!opts.disabled) {
        	input.bind('click.combobox', function(e) {
	            togglePanel(target);
	        }).bind('keydown.combobox', function(e) {
                switch (e.keyCode) {
                    case 37: //left
                    case 38: //up
                        nav(target, 'prev');
                        break;
                    case 39: //right
                    case 40: //down
                        nav(target, 'next');
                        break;
                    case 13: //enter
                        onEnter(target);
                        break;
                    case 9: //tab
                    case 27: //esc
                        hidePanel(target);
                        break;
                    default:
                        if (state.timer) {
                            clearTimeout(state.timer);
                        }
                        state.timer = setTimeout(function() {
                            var q = input.val();
                            if (state.previousValue != q) {
                                state.previousValue = q;
                                showPanel(target);
                                onQuery(target, q);
                            }
                            if(e.keyCode == 8 && q == '') { //backspace
                            	clear(target);
                            }
                        }, opts.delay);
                        break;
                }
            });
            
            //下拉箭头
            arrow.bind('click.combobox', function() {
                //聚焦且光标放到文本最后
                input.focus().val(input.val());
                togglePanel(target);
            });
            
            //下拉选项
            items.bind('mouseover.combobox', function() {
                $(this).addClass('combobox-item-hover');
            }).bind('mouseout.combobox', function() {
                $(this).removeClass('combobox-item-hover');
            }).bind('click.combobox', function(e) {
                var value = $(this).attr('data-value');
                select(target, value);
                hidePanel(target);
            });
        }
    }

    //显示下拉面板
    function showPanel(target) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = state.combo;
        var panel = state.panel;
		
        panel.css({
            left: calcLeft(),
            top: calcTop(),
            display: 'block',
            width: combo.width(),
            height: opts.panelHeight-2,
            zIndex: ($.fn.dialog ? $.fn.dialog.defaults.zIndex++ : opts.zIndex)
        });
        
        scrollTo(target, $(target).combobox('getValue'));
              
        function calcLeft() {
            var left = combo.offset().left;
            
            if (left + combo.outerWidth() > $(window).outerWidth() + $(document).scrollLeft()) {
                left = $(window).outerWidth() + $(document).scrollLeft() - combo.outerWidth();
            }
            if (left < 0) {
                left = 0;
            }
            return left;
        };
        
        function calcTop() {
        	var top = combo.offset().top + combo.outerHeight();
            if (top + opts.panelHeight > $(window).outerHeight() + $(document).scrollTop()) {
                top = combo.offset().top - opts.panelHeight;
            }
            if (top < $(document).scrollTop()) {
                top = combo.offset().top + combo.outerHeight();
            }
            return top;
        }
    }

    //隐藏下拉面板
    function hidePanel(target) {
        var panel = $.data(target, 'combobox').panel;
        panel.css('display', 'none');
    }

    //切换下拉面板
    function togglePanel(target) {
        var panel = $.data(target, 'combobox').panel;

        if (panel.is(':visible')) {
            hidePanel(target);
        } else {
            showPanel(target);
        }
    }

    function findDataItem(data, key, value) {
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            if (item[key] == value) {
                return item
            }
        }
        return null;
    }

    function scrollTo(target, value) {
        var state = $.data(target, 'combobox');
        var panel = state.panel;
		
		var item = panel.children('li.combobox-item[data-value="' + value + '"]');
        if (item.length) {
            if (item.position().top <= 0) {
                var h = panel.scrollTop() + item.position().top;
                panel.scrollTop(h);
            } else if (item.position().top + item.outerHeight() > panel.height()) {
                var h = panel.scrollTop() + item.position().top + item.outerHeight() - panel.height();
                panel.scrollTop(h);
            }
        }
    }

    function nav(target, dir) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var panel = state.panel;

        var item = panel.children('li.combobox-item-hover');
        if (!item.length) {
            item = panel.children('li.combobox-item-selected');
        }
        item.removeClass('combobox-item-hover');
        if (!item.length) {
            item = panel.children('li.combobox-item:visible:' + (dir == 'next' ? 'first' : 'last'));
        } else {
            if (dir == 'next') {
                item = item.nextAll('li.combobox-item:visible:first');
                if (!item.length) {
                    item = panel.children('li.combobox-item:visible:first');
                }
            } else {
                item = item.prevAll('li.combobox-item:visible:first');
                if (!item.length) {
                    item = panel.children('li.combobox-item:visible:last');
                }
            }
        }
        
        if (item.length) {
            item.addClass('combobox-item-hover');
            scrollTo(target, item.attr('data-value'));
        }
    }

    function select(target, value) {
    	var state = $.data(target, 'combobox');
        var opts = state.options;
        var data = state.data;
        
        setValue(target, value);

        var item = findDataItem(data, opts.valueField, value);
        if (item) {
            opts.onSelect.call(target, item);
        }
    }

    function setDisabled(target, disabled) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = state.combo;
        
        if (disabled) {
            opts.disabled = true;
            combo.children('input.combobox-text').attr('disabled', 'disabled');
        } else {
            opts.disabled = false;
            combo.children('input.combobox-text').removeAttr('disabled');
        }
    }

    function getValue(target) {
        var state = $.data(target, 'combobox');
        var combo = state.combo;

        return combo.children('input.combobox-value').val();
    }

    function setValue(target, value, remainText) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var data = state.data;
        var combo = state.combo;
        var panel = state.panel;
        state.previousValue = undefined; //清空query
        
        var oldValue = getValue(target);

        var item = findDataItem(data, opts.valueField, value);
        var text;
        if (item) {
            text = opts.formatter ? opts.formatter.call(target, item) : item[opts.textField];
        }
        panel.children('li.combobox-item-selected').removeClass('combobox-item-selected');
        panel.children('li.combobox-item[data-value="' + value + '"]').addClass('combobox-item-selected');
        //值
        combo.children('input.combobox-value').val(value);
        //文本
        if(!remainText) {
        	combo.children('input.combobox-text').val(text);
    	}
        
        if(oldValue != value) {
       		opts.onChange.call(target, value, oldValue);
       	}
       	$('.combobox-text', combo).validatebox('validate');
    }
    
    function clear(target) {
        var state = $.data(target, 'combobox');
        var combo = state.combo;
        var panel = state.panel;
        //state.previousValue = undefined; //清空query
        
        //panel.children('li.combobox-item').removeClass('combobox-item-selected').show();
        //combo.children('input.combobox-value,input.combobox-text').val('');
        setValue(target, '');
    }

    //加载数据
    function loadData(target, data, remainText) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var panel = state.panel;
        
        data = data || [];
        state.data = opts.loadFilter.call(target, data);
        data = state.data;

        var dd = [];
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            var v = item[opts.valueField];
            var s = item[opts.textField];

            dd.push('<li class="combobox-item" data-value="' + v + '">');
            dd.push(opts.formatter ? opts.formatter.call(target, item) : s);
            dd.push('</li>');
        }
        
        panel.html(dd.join(''));
        //panel[0].innerHTML = dd.join('');
		
		setValue(target, getValue(target), remainText);

        opts.onLoadSuccess.call(target, data);
    }

    //请求远程数据
    function request(target, url, param, remainText) {
        var opts = $.data(target, 'combobox').options;
        if (url) {
            opts.url = url;
        }
        if (!opts.url)
            return;
        param = param || {};

        if (opts.onBeforeLoad.call(target, param) == false)
            return;

        $.ajax({
            type: opts.method,
            url: opts.url,
            data: $.toJSON(param),
            contentType: 'application/json',
            dataType: 'json',
            success: function(data) {
            	if(data.code == 0) {
            		loadData(target, data.rows, remainText);
                	bindEvents(target);
            	}
            },
            error: function() {
                opts.onLoadError.apply(this, arguments);
            }
        });
    }

    function onQuery(target, q) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var data = state.data;
        var panel = state.panel;
        
        if (opts.mode == 'remote') {
            request(target, null, {
                q: q
            }, true);
        } else {
            panel.children('li.combobox-item').hide();
            for (var i = 0; i < data.length; i++) {
                var item = data[i];
                if (opts.filter.call(target, q, item)) {
                    var v = item[opts.valueField];
                    panel.children('li.combobox-item[data-value="' + v + '"]').show();
                }
            }
        }
    }

    function onEnter(target) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var data = state.data;
        var panel = state.panel;

        var item = panel.children('li.combobox-item-hover');
        if (!item.length) {
            item = panel.children('li.combobox-item-selected');
        }
        if (!item.length) {
            return;
        }

        select(target, item.attr('data-value'));
        hidePanel(target);
    }

    $.fn.combobox = function(options, param) {
        if (typeof options == 'string') {
            return $.fn.combobox.methods[options](this, param);
        }

        options = options || {};
        return this.each(function() {
            var state = $.data(this, 'combobox');
            var opts;
            if (state) {
                opts = $.extend(state.options, options);
            } else {
                opts = $.extend({}, $.fn.combobox.defaults, $.fn.combobox.parseOptions(this), options);
                state = $.data(this, 'combobox', {
                    options: opts,
                    data: []
                });
                
                state.combo = $(this);
                if(opts.initDom) {
                	initDom(this);
                }
            }
            
            init(this);
            
            if (opts.data) {
                loadData(this, opts.data);
            }
            request(this);

            setDisabled(this, opts.disabled);
            //setSize(this);
            bindEvents(this);
        });
    };


    $.fn.combobox.methods = {
        options: function(jq) {
            return $.data(jq[0], 'combobox').options;
        },
        getData: function(jq) {
            return $.data(jq[0], 'combobox').data;
        },
        panel: function(jq) {
            return $.data(jq[0], 'combobox').panel;
        },
        getValue: function(jq) {
            return getValue(jq[0]);
        },
        setValue: function(jq, value) {
            return jq.each(function() {
                setValue(this, value);
            });
        },
        disable: function(jq) {
            return jq.each(function() {
                setDisabled(this, true);
                bindEvents(this);
            });
        },
        enable: function(jq) {
            return jq.each(function() {
                setDisabled(this, false);
                bindEvents(this);
            });
        },
        clear: function(jq) {
            return jq.each(function() {
                clear(this);
            });
        },
        loadData: function(jq, data) {
            return jq.each(function() {
                loadData(this, data);
                bindEvents(this);
            });
        },
        reload: function(jq, url) {
            return jq.each(function() {
                request(this, url);
            });
        },
        select: function(jq, value) {
            return jq.each(function() {
                select(this, value);
            });
        }
    };

    $.fn.combobox.parseOptions = function(target) {
        return $.extend({}, $.parser.parseOptions(target,['width']));
    };

    $.fn.combobox.defaults = {
    	initDom: false,
    	zIndex: null,
        panelWidth: null,
        panelHeight: 200,
        disabled: false,
        valueField: 'value',
        textField: 'text',
        mode: 'local', // or 'remote'
        method: 'post',
        url: null,
        data: null,
        hasArrow: true,
        delay: 200,
        validateOptions: {
            handler: {
	            tip: function(target) {
	            	//返回jquery对象
	                return $(target).parent();
	            }
	        }
        },
        filter: function(q, row) {
            var opts = $(this).combobox('options');
            return row[opts.textField].toLowerCase().indexOf(q.toLowerCase()) >= 0;
        },
        formatter: function(row) {
            var opts = $(this).combobox('options');
            return row[opts.textField];
        },
        loadFilter: function(data) {
            return data;
        },
        onBeforeLoad: function(param) {},
        onLoadSuccess: function() {},
        onLoadError: function() {},
        onSelect: function(record) {},
        onChange: function(newValue, oldValue) {}
    };
})(jQuery);